package cn.fufeiii.bt.common.util;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

/**
 * BigDecimalCollectorsUtil
 *
 * @author FuFei
 */
public final class BigDecimalCollectorsUtil {

    private static final Set<Collector.Characteristics> CH_NOID = Collections.emptySet();

    private BigDecimalCollectorsUtil() {
    }

    /**
     * self to self
     */
    @SuppressWarnings("unchecked")
    private static <I, R> Function<I, R> castingIdentity() {
        return i -> (R) i;
    }

    /**
     * 求和
     */
    public static <T> Collector<T, ?, BigDecimal> summing(Function<? super T, BigDecimal> mapper) {
        return new CollectorImpl<>(
                () -> new BigDecimal[]{BigDecimal.ZERO},
                (a, t) -> a[0] = a[0].add(mapper.apply(t), MathContext.DECIMAL32),
                (a, b) -> {
                    a[0] = a[0].add(b[0], MathContext.DECIMAL32);
                    return a;
                },
                a -> a[0],
                CH_NOID);
    }

    /**
     * 求最大
     * 这里Integer.MIN_VALUE作为初始条件判断值，如果某些数据范围超过百亿以后
     * 可以根据需求换成Long.MIN_VALUE或者Double.MIN_VALUE
     */
    public static <T> Collector<T, ?, BigDecimal> max(Function<? super T, BigDecimal> mapper) {
        return new CollectorImpl<>(
                () -> new BigDecimal[]{new BigDecimal(Integer.MIN_VALUE)},
                (a, t) -> a[0] = a[0].max(mapper.apply(t)),
                (a, b) -> {
                    a[0] = a[0].max(b[0]);
                    return a;
                },
                a -> a[0],
                CH_NOID);
    }

    /**
     * 求最小
     * 这里Integer.MAX_VALUE作为初始条件判断值，如果某些数据范围超过百亿以后
     * 可以根据需求换成Long.MAX_VALUE或者Double.MAX_VALUE
     */
    public static <T> Collector<T, ?, BigDecimal> min(Function<? super T, BigDecimal> mapper) {
        return new CollectorImpl<>(
                () -> new BigDecimal[]{new BigDecimal(Integer.MAX_VALUE)},
                (a, t) -> a[0] = a[0].min(mapper.apply(t)),
                (a, b) -> {
                    a[0] = a[0].min(b[0]);
                    return a;
                },
                a -> a[0],
                CH_NOID);
    }

    /**
     * 求平均，并且保留小数
     */
    public static <T> Collector<T, ?, BigDecimal> averaging(Function<? super T, BigDecimal> mapper, int newScale, RoundingMode roundingMode) {
        return new CollectorImpl<>(
                () -> new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO},
                (a, t) -> {
                    a[0] = a[0].add(mapper.apply(t));
                    a[1] = a[1].add(BigDecimal.ONE);
                },
                (a, b) -> {
                    a[0] = a[0].add(b[0]);
                    return a;
                },
                a -> a[0].divide(a[1], MathContext.DECIMAL32).setScale(newScale, roundingMode),
                CH_NOID);
    }

    /**
     * 求平均，小数位四舍五入
     */
    public static <T> Collector<T, ?, BigDecimal> averagingHalfUp(Function<? super T, BigDecimal> mapper, int newScale) {
        return averaging(mapper, newScale, RoundingMode.HALF_UP);
    }

    /**
     * Simple implementation class for {@link Collector}.
     *
     * @param <T> the type of elements to be collected
     * @param <A> the mutable accumulation type of the reduction operation (often hidden as an implementation detail)
     * @param <R> the type of the result
     */
    static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
        private final Supplier<A> supplier;
        private final BiConsumer<A, T> accumulator;
        private final BinaryOperator<A> combiner;
        private final Function<A, R> finisher;
        private final Set<Characteristics> characteristics;

        CollectorImpl(Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner,
                      Function<A, R> finisher, Set<Characteristics> characteristics) {
            this.supplier = supplier;
            this.accumulator = accumulator;
            this.combiner = combiner;
            this.finisher = finisher;
            this.characteristics = characteristics;
        }

        CollectorImpl(Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner,
                      Set<Characteristics> characteristics) {
            this(supplier, accumulator, combiner, castingIdentity(), characteristics);
        }

        @Override
        public Supplier<A> supplier() {
            return supplier;
        }

        @Override
        public BiConsumer<A, T> accumulator() {
            return accumulator;
        }

        @Override
        public BinaryOperator<A> combiner() {
            return combiner;
        }

        @Override
        public Function<A, R> finisher() {
            return finisher;
        }

        @Override
        public Set<Characteristics> characteristics() {
            return characteristics;
        }
    }

}